package oracle

import (
	"context"
	"strings"
	"sync"

	"testkjcld.bhecard.com/gitlab/kit/esim/config"
	"testkjcld.bhecard.com/gitlab/kit/esim/log"

	_ "github.com/sijms/go-ora/v2"
)

var clientOnce sync.Once

var onceClient *Client

type Client struct {
	dbIns map[string]*OraDB

	conf config.Config

	logger log.Logger

	dbConfigs []DbConfig
}

type Option func(c *Client)

type ClientOptions struct{}

type DbConfig struct {
	Db          string `json:"db" yaml:"db"`
	Schema      string `json:"schema" yaml:"schema"`
	Dsn         string `json:"dsn" yaml:"dsn"`
	MaxIdle     int    `json:"max_idle" yaml:"maxidle"`
	MaxOpen     int    `json:"max_open" yaml:"maxopen"`
	MaxLifetime int    `json:"max_lifetime" yaml:"maxlifetime"`
}

func NewClient(options ...Option) *Client {
	clientOnce.Do(func() {
		onceClient = &Client{
			dbIns: make(map[string]*OraDB),
		}

		for _, option := range options {
			option(onceClient)
		}

		if onceClient.conf == nil {
			onceClient.conf = config.NewNullConfig()
		}

		if onceClient.logger == nil {
			onceClient.logger = log.NewLogger()
		}

		onceClient.init()
	})

	return onceClient
}

func (ClientOptions) WithConf(conf config.Config) Option {
	return func(m *Client) {
		m.conf = conf
	}
}

func (ClientOptions) WithLogger(logger log.Logger) Option {
	return func(m *Client) {
		m.logger = logger
	}
}

func (ClientOptions) WithDbConfig(dbConfigs []DbConfig) Option {
	return func(m *Client) {
		m.dbConfigs = dbConfigs
	}
}

// initializes Client.
func (c *Client) init() {
	dbConfigs := make([]DbConfig, 0)
	err := c.conf.UnmarshalKey("oracle", &dbConfigs)
	if err != nil {
		c.logger.Panicf("Fatal error config file: %s \n", err.Error())
	}

	if len(c.dbConfigs) > 0 {
		dbConfigs = append(dbConfigs, c.dbConfigs...)
	}

	setOraEnv()

	for _, dbConfig := range dbConfigs {
		oraDB := NewOraDB(c.logger, &dbConfig)
		c.setDb(dbConfig.Db, oraDB)
		c.logger.Infof("[oracle] %s init success", dbConfig.Db)
	}
}

func (c *Client) setDb(dbName string, db *OraDB) {
	dbName = strings.ToLower(dbName)
	c.dbIns[dbName] = db
}

func (c *Client) GetDb(dbName string) *OraDB {
	return c.getDb(dbName)
}

func (c *Client) GetCtxDb(ctx context.Context, dbName string) *OraDB {
	return c.getDb(dbName)
}

func (c *Client) getDb(dbName string) *OraDB {
	dbName = strings.ToLower(dbName)
	if db, ok := c.dbIns[dbName]; ok {
		return db
	}

	c.logger.Errorf("[db] %s not found", dbName)
	return nil
}

func (c *Client) Ping() []error {
	var errs []error
	var err error
	for _, db := range c.dbIns {
		err = db.Ping()
		if err != nil {
			errs = append(errs, err)
		}
	}

	return errs
}

func (c *Client) Close() {
	for name, db := range c.dbIns {
		err := db.Close()
		if err != nil {
			c.logger.Errorf("%s:%v", name, err.Error())
		}
	}
}
